# Specifické příkazy pro prostředí Google Colab
if 'google.colab' in str(get_ipython()):
import os, sys
os.chdir('/content')
# Stažení knihovny
! ls parlamentikon || git clone "https://github.com/parlamentikon/parlamentikon.git" --branch main
os.chdir('/content/parlamentikon/notebooks')
instalace_zavislosti = True
if instalace_zavislosti:
! pip install -r ../requirements.txt 1>/dev/null
instalace_knihovny = False
if instalace_knihovny:
! pip install .. 1>/dev/null
else:
# Přidání cesty pro lokální import knihovny
import sys, os
sys.path.insert(0, os.path.abspath('..'))
from datetime import datetime
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from parlamentikon.Hlasovani import Hlasovani, ZpochybneniHlasovani, HlasovaniPoslanci
from nastav_notebook import nastav_pandas
# Data se budou pokaždé znovu stahovat z achivu PS
stahni=True
# Budeme analyzovat poslední volební období
zvolene_volebni_obdobi = None
# Načti souhrnné informace o hlasováních
h = Hlasovani(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
h.head(2)
2021-03-23:10:21:56 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-03-23:10:21:58 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/hl-2017ps.zip'.
| id_hlasovani | id_organ | schuze | cislo | bod | cas | pro | proti | zdrzel | nehlasoval | ... | nazev_kratky | datum | bod__KAT | vysledek | druh_hlasovani | ma_zpochybneni | je_zmatecne | ma_stenozaznam | turn | typ | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 67018 | 172 | 1 | 1 | 3 | 13:53:00 | 191 | 0 | 5 | 0 | ... | <NA> | 2017-11-20 13:53:00+01:00 | normální | přijato | normální | False | True | False | <NA> | <NA> |
| 1 | 67019 | 172 | 1 | 2 | 3 | 13:53:00 | 194 | 0 | 4 | 0 | ... | <NA> | 2017-11-20 13:53:00+01:00 | normální | přijato | normální | False | False | False | <NA> | <NA> |
2 rows × 23 columns
# Načti informace o zpochybnění hlasování
zph = ZpochybneniHlasovani(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
zph.head(2)
2021-03-23:10:22:02 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-03-23:10:22:03 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/hl-2017ps.zip'. 2021-03-23:10:22:07 WARNING [Snemovna.py:149] While merging 'zpochybneni' with 'hlasovani': Dropping ['turn__hlasovani'] because of abundance. 2021-03-23:10:22:07 WARNING [Snemovna.py:184] Pro sloupec 'je_platne' nebyla nalezena metadata!
| id_hlasovani | turn | mode | id_h2 | id_h3 | mode__KAT | je_platne | id_organ | schuze | cislo | ... | nazev_kratky | datum | bod__KAT | vysledek | druh_hlasovani | ma_zpochybneni | je_zmatecne | ma_stenozaznam | typ__ORIG | typ | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 55680 | 67 | 0 | 55681 | 55682 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN | <NA> | <NA> |
| 1 | 55664 | 43 | 0 | 55665 | 55666 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN | <NA> | <NA> |
2 rows × 32 columns
# Načti indiviuální hlasování poslanců
hp = HlasovaniPoslanci(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
hp.head(2)
2021-03-23:10:22:07 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-03-23:10:22:09 WARNING [Snemovna.py:149] While merging 'funkce' with 'typ_funkce': Dropping ['nazev_typ_organ_cz__typ_funkce', 'id_typ_organ__typ_funkce', 'nazev_typ_organ_en__typ_funkce', 'typ_id_typ_organ__typ_funkce', 'typ_organ_obecny__typ_funkce'] because of abundance. 2021-03-23:10:22:10 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/hl-2017ps.zip'.
| id_hlasovani | nazev_dlouhy | vysledek | id_poslanec | id_osoba | pred | jmeno | prijmeni | id_klub | nazev_klub_cz | ... | cas | datum | bod__KAT | druh_hlasovani | ma_zpochybneni | id_parlament | id_organ | od_klub | do_klub | je_zmatecne | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| index | |||||||||||||||||||||
| 0 | 67018 | Inf. o ustavení volební komise PS a volbě členů | ano | 1521 | 5700 | Mgr. | Ivan | Adamec | 1295 | Poslanecký klub Občanské demokratické strany | ... | 13:53:00 | 2017-11-20 13:53:00+01:00 | normální | normální | False | 172 | 172 | 2017-10-24 00:00:00+02:00 | NaT | True |
| 1 | 67018 | Inf. o ustavení volební komise PS a volbě členů | ano | 1522 | 6254 | prof. MUDr. | Věra | Adámková | 1292 | Poslanecký klub ANO 2011 | ... | 13:53:00 | 2017-11-20 13:53:00+01:00 | normální | normální | False | 172 | 172 | 2017-10-24 00:00:00+02:00 | NaT | True |
2 rows × 33 columns
volebni_obdobi = h.volebni_obdobi
snemovna = h.snemovna
print(f"Poslanecká sněmovna bude analyzovaná pro volební období {volebni_obdobi}.")
Poslanecká sněmovna bude analyzovaná pro volební období 2017.
Přibližná pravidla pro určení platnosti hlasování:
def flatten(ary):
return [x for l in ary for x in l]
# Hlasování o zpochybnění hlasování je možné také zkazit nebo zpochybnit.
# Mezi prvním hlasováním o zpochybnění a opakovaným hlasováním může proběhnout několik dalších zpochybněných nebo neplatných hlasování.
def fce_mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovanim_ids(row):
if pd.isna(row['id_h2']):
return []
elif pd.isna(row['id_h3']):
return []
else:
return list(range(row['id_h2']+1, row['id_h3']))
zpochybneni = zph[zph.je_platne == True]
mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovani_ids = \
flatten(zpochybneni[zpochybneni.mode__KAT == 'žádost o opakování'].apply(fce_mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovanim_ids, axis=1))
hlasovani_o_zpochybneni_ids = h[h.id_hlasovani.isin(zpochybneni.id_h2.unique())]
hlasovani_bez_zmatecnych_a_zpochybnenych = h[~h.je_zmatecne
& ~h.id_hlasovani.isin(zph[zph.mode__KAT == 'žádost o opakování'].id_hlasovani)
& ~h.id_hlasovani.isin(mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovani_ids)
& ~h.id_hlasovani.isin(hlasovani_o_zpochybneni_ids)
]
hlasovani_bez_zmatecnych_a_zpochybnenych.groupby('nazev_dlouhy').size().sort_values()
nazev_dlouhy
Návrh na volbu členů DR Státního fondu dopravní infrastruktury 1
Návrh na volbu člena DR SZIF 1
Návrh na volbu čl. SK pro hybridní hrozby 1
Návrh na volbu předsedy SK pro kontrolu činnosti GIBS 1
Návrh na volbu předsedy DR SZIF 1
...
Návrh na vyslovení souhlasu PS s prodloužením doby nouzového stavu 159
Vl.n.z. o státním rozpočtu ČR na rok 2020 190
228
Novela z. o evidenci tržeb - EU 857
Pořad schůze 1336
Length: 748, dtype: int64
%%time
from collections import OrderedDict
# Vyber jenom platná hlasování
h_platne = hlasovani_bez_zmatecnych_a_zpochybnenych #[hlasovani_bez_zmatecnych_a_zpochybnenych.nazev_dlouhy == 'Novela z. o evidenci tržeb - EU']
df = hp[hp.id_hlasovani.isin(h_platne.id_hlasovani)]
# Seřaď osoby podle posl. klubu a shlukni hlasování podle osoby
data = df[['id_osoba', 'id_hlasovani', 'vysledek', 'zkratka_klub']]\
.set_index('id_osoba')\
.sort_values(by=['zkratka_klub'])[['id_hlasovani', 'vysledek']]\
.groupby('id_osoba', sort=False)\
.apply(lambda g: list(map(tuple, g.values))).to_dict(into=OrderedDict)
# Vyber informace k dané osobě. Bereme v úvahu poslední klub, do kterého poslanec/kyně patří.
osoby = {id_osoba: hp[hp.id_osoba == id_osoba][['jmeno', 'prijmeni', 'zkratka_kandidatka','nazev_kraj_cz', 'zkratka_klub']].iloc[-1] for id_osoba in data.keys()}
# Pro každou osobu připrav pole výsledků hlasování
hlasovani_osoby = {osoba: set([str(hl_idx) + '_' + vysledek for hl_idx, vysledek in data[osoba]]) for osoba in data.keys()}
CPU times: user 9.33 s, sys: 252 ms, total: 9.58 s Wall time: 9.58 s
%%time
def jaccard_similarity(set1, set2):
intersection_size = len(set1.intersection(set2))
union = len(set1) + len(set2) - intersection_size
return float(intersection_size) / union
matice_poslanci = {}
for osoba1, v1 in hlasovani_osoby.items():
if osoba1 not in matice_poslanci:
matice_poslanci[osoba1] = {}
for osoba2, v2 in hlasovani_osoby.items():
similarity = jaccard_similarity(v1, v2)
matice_poslanci[osoba1][osoba2] = similarity
CPU times: user 29.1 s, sys: 15.1 ms, total: 29.1 s Wall time: 29.1 s
osoby_data = pd.DataFrame([
{
'jmeno_prijmeni': f"{osoby[id_osoba].jmeno} {osoby[id_osoba].prijmeni}",
'id_osoba': id_osoba,
'zkratka_klub': osoby[id_osoba].zkratka_klub
}
for id_osoba in matice_poslanci.keys()
])
osoby_data['velikost_skupiny'] = osoby_data.groupby('zkratka_klub').id_osoba.transform(len)
osoby_data['poradi_v_skupine'] = osoby_data.groupby('zkratka_klub').cumcount() + 1
osoby_data['je_na_okraji'] = (osoby_data.poradi_v_skupine == 1) | (osoby_data.velikost_skupiny == osoby_data.poradi_v_skupine)
osoby_data['je_uprostred'] = osoby_data.poradi_v_skupine == (osoby_data.velikost_skupiny/2).astype(int)
text = [
[
f"{v1.jmeno_prijmeni} ({v1.zkratka_klub})<br>"
f"{v2.jmeno_prijmeni} ({v2.zkratka_klub})<br>"
f"Podobnost hlasování: {100*matice_poslanci[v1.id_osoba][v2.id_osoba]:.0f}%" for idx2, v2 in osoby_data.iterrows()
] for idx1, v1 in osoby_data.iterrows()
]
fig = go.Figure(data=go.Heatmap(
z=pd.DataFrame(matice_poslanci),
x=osoby_data.id_osoba.values,
y=osoby_data.id_osoba.values,
text=text,
colorscale='Viridis',
hovertemplate="%{text}<extra></extra>"
))
fig.update_layout(title='Podobnost hlasování poslanců', width=1000, height=1000)
#fig.update_xaxes(type='category', tickangle=45, tickmode='array', ticktext=label_strana, tickvals=label)
for zkratka_klub in osoby_data.zkratka_klub.unique():
fig.update_xaxes(
type='category', tickangle=-45,
side='top', overlaying='x',
tickmode='array', ticktext=osoby_data[osoby_data.je_uprostred].zkratka_klub, tickvals=osoby_data[osoby_data.je_uprostred].id_osoba,
showspikes=True,
spikemode='toaxis'
)
fig.update_yaxes(
type='category', autorange='reversed',
tickmode='array', ticktext=osoby_data[osoby_data.je_uprostred].zkratka_klub, tickvals=osoby_data[osoby_data.je_uprostred].id_osoba,
showspikes=True,
spikemode='toaxis'
)
fig.show()
matice_kluby = {}
for id_osoba1, values in matice_poslanci.items():
klub1 = osoby[id_osoba1].zkratka_klub
if klub1 not in matice_kluby:
matice_kluby[klub1] = {}
for id_osoba2, podobnost in values.items():
klub2 = osoby[id_osoba2].zkratka_klub
if klub2 not in matice_kluby[klub1]:
matice_kluby[klub1][klub2] = []
matice_kluby[klub1][klub2].append(podobnost)
matice_kluby_median = pd.DataFrame({ klub1: {klub2: np.median(ary) for klub2, ary in values.items()} for klub1, values in matice_kluby.items()})
matice_kluby_prumer = pd.DataFrame({ klub1: {klub2: np.mean(ary) for klub2, ary in values.items()} for klub1, values in matice_kluby.items()})
fig = go.Figure(data=go.Heatmap(
z=matice_kluby_prumer,
x=list(matice_kluby_prumer.keys()),
y=list(matice_kluby_prumer.keys()),
colorscale='Viridis',
hovertemplate="%{x}<br>%{y}<br>Podobnost hlasování: %{z}<extra></extra>"
))
fig.update_layout(
title='Podobnost hlasování dle poslaneckých klubů',
width=800,
height=800,
autosize=False,
margin=dict(t=150, b=0, l=0, r=0),
template="plotly_white",
)
fig.update_xaxes(
type='category', tickangle=-45,
side='top',
)
fig.update_yaxes(
type='category', autorange='reversed',
)
updatemenus = [{
'buttons': [
{'method': 'update', 'label': 'průměr', 'args': [{'z': [matice_kluby_prumer.values]}]},
{'method': 'update', 'label': 'medián', 'args': [{'z': [matice_kluby_median.values]}]}
],
'direction': 'down',
'showactive': True,
'x': 1.1, 'xanchor': 'right', 'y': 1.15, 'yanchor': 'top',
}]
# update layout with buttons, and show the figure
fig.update_layout(updatemenus=updatemenus)
fig.show()
print(f"Poslední běh notebooku: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}.")
Poslední běh notebooku: 23.03.2021 10:23:21.